#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include "rfs-defs.h"

/****************************************************************/
/*  Función que recibe un paquete                               */

/****************************************************************/
int recibir(PACKAGE *package, struct sockaddr_in *sock, int flag) {
    int n;
    fd_set tmp_set;
    FD_ZERO(&tmp_set);
    FD_SET(package->to, &tmp_set);
    // el flag y el valor de la estructura sock me permiten distiguir si es un llamado desde TCP o UDP
    if (sock != NULL || flag == MSG_DONTWAIT) {
        // recibo en UDP
        int sock_size = sizeof (*sock);
        // recibo el paquete
        if (flag != 0) {
            // con timeout

            if (select(package->to + 1, &tmp_set, NULL, NULL, timeout) < 0) {
                perror("Timeout o error recibiendo paquete.\n");
            } else {
                n = recvfrom(package->to, package, sizeof (PACKAGE), flag, (struct sockaddr *) sock, &sock_size);
            }

        } else {
            // sin timeout, bloqueante
            if (select(package->to + 1, &tmp_set, NULL, NULL, NULL) < 0) {
                perror("Error recibiendo paquete.\n");
            } else {
                n = recvfrom(package->to, package, sizeof (PACKAGE), flag, (struct sockaddr *) sock, &sock_size);
            }
        }
    } else {
        // recibo en TCP
        if (select(package->to + 1, &tmp_set, NULL, NULL, NULL) < 0) {
            perror("Error recibiendo paquete.\n");
        } else {
            n = read(package->to, package, sizeof (PACKAGE));
        }
    }
    FD_ZERO(&tmp_set);
    return n;
}

/****************************************************************/
/*  Función que envía un paquete                                */

/****************************************************************/
int enviar(PACKAGE *package, struct sockaddr_in *sock, int mode) {
    int count = 0, i = 0, j = 0;
    // la estructura sock me permite distinguir si envio sobre TCP o UDP
    if (sock != NULL) {
        // envio sobre UDP
        if (mode == ACK) {
            // con espera de ACK
            do {
                count = sendto(package->to, package, sizeof (PACKAGE), 0, (struct sockaddr *) sock, sizeof (*sock));
                if ((i = recibir(package, NULL, MSG_DONTWAIT)) < 0) {
                    perror("Ocurrió un error recibiendo el ACK.\n");
                    return ERROR;
                } else
                    if (package->op != ACK) {
                    printf("El cliente envió '%i' cuando se esperaba ACK.\n", package->op);
                    return ERROR;
                }
            } while (i < 0 && j++ < RE_ATTEMPTS);
            if (i < 0)
                return ERROR;
        } else {
            // sin espera de ACK
            count = sendto(package->to, package, sizeof (PACKAGE), 0, (struct sockaddr *) sock, sizeof (*sock));
        }
    } else {
        // envio sobre TCP
        count = write(package->to, package, sizeof (PACKAGE));
    }
    return count;
}

/****************************************************************/
/*  Función que segmenta una cadena en bloques de longitud      */
/*  DATA_LENGTH y los envía                                     */
/*  Retorna la cantidad de bytes transmitidos.                  */

/****************************************************************/
long segmentar(char * data, PACKAGE * package, struct sockaddr_in *sock, unsigned long size) {
    long i = 0;
    for (; (i * DATA_LENGTH) < size; i++) {
        package->offset = ((i + 1) * DATA_LENGTH) < size ? DATA_LENGTH : (size - (i * DATA_LENGTH));
        memcpy(package->data, &data[i * DATA_LENGTH], package->offset);
        if (enviar(package, sock, ACK) == -1) {
            perror("Ocurrió un error enviando bloque en la segmentación.\n");
            return ERROR;
        }
    }
    return i * DATA_LENGTH + package->offset;
}

/****************************************************************/
/*  Función que ensambla una cadena de bloques de longitud      */
/*  DATA_LENGTH a medida que los va recibiendo                  */
/*  Retorna la cantidad de bytes recibidos.                     */

/****************************************************************/
long ensamblar(char ** data, PACKAGE * package, struct sockaddr_in *sock) {
    int i = 0;
    do {
        if (recibir(package, NULL, MSG_DONTWAIT) < 0)
            return ERROR;
        *data = (char *) realloc(*data, i * DATA_LENGTH + package->offset);
        memmove(&(*data)[i * DATA_LENGTH], package->data, package->offset);
        if (sock != NULL) {
            package->op = ACK;
            if (enviar(package, sock, 0) < 0) {
                perror("Ocurrió un error enviando ACK en el ensamble.\n");
                return ERROR;
            }
        }
        i++;
    } while (package->offset == DATA_LENGTH);
    return (i - 1)*DATA_LENGTH + package->offset;
};

int create_socket(long port, int protocol) {

    int sd;
    struct sockaddr_in dir_srv;
    /* Creo un descriptor de archivo. */
    if ((sd = socket(AF_INET, protocol, 0)) < 0)
        return ERROR;

    /* Hago un bind de la direccion local */
    bzero((char *) & dir_srv, sizeof (dir_srv));
    dir_srv.sin_family = AF_INET;
    dir_srv.sin_addr.s_addr = INADDR_ANY;
    dir_srv.sin_port = htons(port);
    if (bind(sd, (struct sockaddr *) & dir_srv, sizeof (dir_srv)) < 0)
        return ERROR;

    if (protocol == SOCK_STREAM)
        listen(sd, MAX_CLIENTS);
    return sd;
}

int close_socket(int sd) {
    return close(sd);
}

int waiting_for_connections(int sd) {
    int my_sd;
    /* Quedo a la espera de una conexion. */
    if ((my_sd = accept(sd, NULL, NULL)) < 0)
        return ERROR;
    return my_sd;
}